﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using CardPlay.Client.Model;
using CardPlay.Model.GameModel;
using CardPlay.Model.GameModel.Actions;
using CardPlay.Model.GameModel.Presentation;
using CardPlay.Model.Layout;
using CardPlay.Model.Primitives;

namespace CardPlay.Client.ViewModels
{
    public class PileViewModel : LayoutArea, ITreeNode, INotifyPropertyChanged
    {
        private readonly CardPile _pile;
        private readonly ObservableCollection<CardViewModel> _childNodes = new ObservableCollection<CardViewModel>();
        private readonly BaseCues _baseCues = new BaseCues();
        private Rectangle _baseBounds;
        private bool _isElevated;

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        #endregion

        public PileViewModel(ITreeNode parent, VisualTraits traits, CardPile pile)
        {
            ParentNode = parent;
            Traits = traits;

            _pile = pile;

            Pile.CollectionChanged += OnModelChanged;

            if (! _pile.IsEmpty)
                OnModelChanged(_pile, NotifyCardPileChangedEventArgs.ForAdd(_pile.ToList(), 0));
        }

        public ITreeNode ParentNode { get; set; }

        public IEnumerable<ITreeNode> ChildNodes
        {
            get { return _childNodes; }
        }

        public object BaseCues
        {
            get { return _baseCues; }
        }

        public object EmptyCue
        {
            get
            {
                //var emptyCue = Traits.EmptyCue != null ? Traits.EmptyCue() : EmptyPileCue.None;
                //return Canvas.ImageForEmptyCue(emptyCue);
                return !IsElevated && Traits.EmptyCue != null ? Traits.EmptyCue() : EmptyPileCue.None;
            }
        }

        public bool IsElevated
        {
            get { return _isElevated; }
            set
            {
                if (value == IsElevated) return;
                _isElevated = value;
                NotifyOfPropertyChange("IsElevated");
            }
        }

        public VisualTraits Traits { get; private set; }

        private ICanvas Canvas
        {
            get { return this.GetService<IPlayingContext>().Canvas; }
        }

        public string Name { get; set; }

        public Rectangle BaseBounds
        {
            get { return _baseBounds; }
            set
            {
                if (value == BaseBounds) return;

                _baseBounds = value;
                NotifyOfPropertyChange("BaseBounds");
            }
        }

        public Rectangle TightestBounds
        {
            get
            {
                var pileBounds = Cards.Select(card => card.Bounds).Aggregate(BaseBounds, Rectangle.Union);
                pileBounds.Offset(Bounds.Location);

                return pileBounds;
            }
        }

        public CardPile Pile
        {
            get { return _pile; }
        }

        public IEnumerable<CardViewModel> Cards
        {
            get { return _childNodes; }
        }

        public void Offset(Size offset)
        {
            Location = Location + offset;
            NotifyOfPropertyChange("Bounds");
        }

        public PileViewModel SplitFor(RemovePileAction action)
        {
            var pile = new PileViewModel(ParentNode, Traits, action.PileBeingRemoved)
            {
                Bounds = Bounds,
                BaseBounds = BaseBounds,
                IsElevated = true
            };

            var oldIndex = action.Location.Index;

            for (var newIndex = 0; newIndex < action.PileBeingRemoved.Count; ++newIndex, ++oldIndex)
            {
                pile._childNodes[newIndex].Location = _childNodes[oldIndex].Location;
                _childNodes[oldIndex].Hidden = true;
            }

            return pile;
        }

        public void RestoreLiftedCards(RemovePileAction action)
        {
            if (!_childNodes.Any()) return;

            for (var i = 0; i < action.PileBeingRemoved.Count; ++i)
                _childNodes[action.Location.Index + i].Hidden = false;
        }

        public void UpdateCues()
        {
            NotifyOfPropertyChange("EmptyCue");
            if (Pile.IsEmpty && Traits.UpdateBaseCues != null) Traits.UpdateBaseCues(Pile, _baseCues);
        }

        public void UpdateLayout()
        {
            if (Traits.Arrangement == null) return;

            var points = Traits.Arrangement(Pile, Canvas.Metrics);
            //Guard.Against<System.InvalidOperationException>(points.Length != _contents.Count, "Out of sync");

            for (var i = 0; i < points.Length; ++i)
            {
                _childNodes[i].Location = BaseBounds.Location + points[i];
            }
        }

        protected override Size OnMeasure(ILayoutContext context, Size availableSize)
        {
            var size = base.OnMeasure(context, availableSize);
            if (Collapsed) return size;

            var cardSize = Canvas.Metrics.CardSize;
            size = cardSize;

            if (Traits.ContentFit != ContentFit.Ignore && Cards.Any())
            {
                var bounds = Cards
                    .Select(card => new Rectangle(card.Location.X, card.Location.Y, cardSize.Width, cardSize.Height))
                    .Aggregate(new Rectangle(), Rectangle.Union).Size;

                if ((Traits.ContentFit & ContentFit.FitWidth) != 0)
                {
                    size.Width = bounds.Width;
                }

                if ((Traits.ContentFit & ContentFit.FitHeight) != 0)
                {
                    size.Height = bounds.Height;
                }
            }

            return size.BeyondLowerLimit(Traits.MinimumSize);
        }

        protected override void OnArrange(ILayoutContext context, Rectangle bounds)
        {
            NotifyOfPropertyChange("Bounds");
            Debug.WriteLine("Arranged: " + Pile.Name + ": " + Bounds);

            var origin = Canvas.Metrics.CardSize.PositionWithin(new Rectangle(Point.Empty, bounds.Size), Traits.HorizontalAlignment, Traits.VerticalAlignment).Location;
                
            //switch (ContentHorizontalAlignment)
            //{
            //    case HorizontalAlignment.Right:
            //        origin.X = Math.Max(0, Bounds.Width - Canvas.Metrics.CardSize.Width);
            //        break;
            //}

            //switch (ContentVerticalAlignment)
            //{
            //    case VerticalAlignment.Bottom:
            //        origin.Y = Math.Max(0, Bounds.Height - Canvas.Metrics.CardSize.Height);
            //        break;
            //}

            BaseBounds = new Rectangle(origin, Canvas.Metrics.CardSize);
            UpdateLayout();
        }

        private void OnModelChanged(object sender, NotifyCardPileChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    for (var i = e.NewStartingIndex; i < e.NewStartingIndex + e.NewItems.Count; ++i)
                        _childNodes.Insert(i, new CardViewModel(this, Pile.CardAt(i)));
                    break;

                case NotifyCollectionChangedAction.Replace:
                    for (var i = e.NewStartingIndex; i < e.NewStartingIndex + e.NewItems.Count; ++i)
                        _childNodes[i] = new CardViewModel(this, Pile.CardAt(i));
                    break;

                case NotifyCollectionChangedAction.Remove:
#pragma warning disable 168
                    foreach (var t in e.OldItems)
#pragma warning restore 168
                    {
                        _childNodes[e.OldStartingIndex].ParentNode = null;
                        _childNodes.RemoveAt(e.OldStartingIndex);
                    }
                    break;

                case NotifyCollectionChangedAction.Reset:
                    _childNodes.Clear();
                    break;
            }

            UpdateLayout();
        }

        private void NotifyOfPropertyChange(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public PartLocation HitTest(Rectangle bounds)
        {
            if (Collapsed || String.IsNullOrEmpty(Pile.Name)) return PartLocation.Empty;

            bounds.X -= Bounds.X;
            bounds.Y -= Bounds.Y;

            for (var i = _childNodes.Count; --i >= 0; )
            {
                if (_childNodes[i].HitTest(bounds))
                {
                    return _childNodes[i].PartLocation;
                }
            }

            return BaseBounds.IntersectsWith(bounds) ? Pile.Location : PartLocation.Empty;
        }
    }
}